/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.graph.impl;

import org.apache.jena.atlas.iterator.Iter;
import org.apache.jena.graph.*;
import org.apache.jena.shared.AddDeniedException;
import org.apache.jena.shared.ClosedException;
import org.apache.jena.shared.DeleteDeniedException;
import org.apache.jena.shared.PrefixMapping;
import org.apache.jena.shared.impl.PrefixMappingImpl;
import org.apache.jena.util.iterator.ClosableIterator;
import org.apache.jena.util.iterator.ExtendedIterator;

/**
 * GraphBase is an implementation of Graph that provides some convenient base
 * functionality for Graph implementations.
 * <p>
 * Subtypes of GraphBase must provide performAdd(Triple), performDelete(Triple),
 * graphBaseFind(TripleMatch,TripleAction), and graphBaseSize(). GraphBase provides
 * default implementations of the other methods, including the other finds (on top of
 * that one), a simple-minded prepare, and contains. GraphBase also handles the
 * event-listening and registration interfaces.
 * <p>
 * When a GraphBase is closed, future operations on it may throw an exception.
 */

public abstract class GraphBase implements GraphWithPerform {
    /**
     * Whether or not this graph has been closed - used to report ClosedExceptions
     * when an operation is attempted on a closed graph.
     */
    protected boolean closed = false;

    /**
     * Initialise this graph.
     */
    public GraphBase() {}

    /**
     * Initialise this graph with the given reification style (which will be supplied
     * to the reifier when it is created).
     */

    /**
     * Utility method: throw a ClosedException if this graph has been closed.
     */
    protected void checkOpen() {
        if ( closed )
            throw new ClosedException("already closed", this);
    }

    /**
     * Close this graph. Subgraphs may extend to discard resources.
     */
    @Override
    public void close() {
        closed = true;
    }

    @Override
    public boolean isClosed() {
        return closed;
    }

    /**
     * Answer the event manager for this graph; allocate a new one if required.
     * Subclasses may override if they have a more specialised event handler. The
     * default is a SimpleEventManager.
     */
    @Override
    public GraphEventManager getEventManager() {
        if ( gem == null )
            gem = new SimpleEventManager();
        return gem;
    }

    /**
     * The event manager that this Graph uses to, well, manage events; allocated on
     * demand.
     */
    protected GraphEventManager gem;

    /**
     * Tell the event manager that the triple <code>t</code> has been added to the
     * graph.
     */
    public void notifyAdd(Triple t) {
        getEventManager().notifyAddTriple(this, t);
    }

    /**
     * Tell the event manager that the triple <code>t</code> has been deleted from
     * the graph.
     */
    public void notifyDelete(Triple t) {
        getEventManager().notifyDeleteTriple(this, t);
    }

    /**
     * Answer a transaction handler bound to this graph. The default is
     * SimpleTransactionHandler, which handles <i>no</i> transactions.
     */
    @Override
    public TransactionHandler getTransactionHandler() {
        return new SimpleTransactionHandler();
    }

    /**
     * Answer the PrefixMapping object for this graph, the same one each time.
     */
    @Override
    public PrefixMapping getPrefixMapping() {
        if ( pm == null )
            pm = createPrefixMapping();
        return pm;
    }

    protected PrefixMapping pm = null;
    protected PrefixMapping createPrefixMapping() {
        return new PrefixMappingImpl();
    }

    /**
     * Add a triple, and notify the event manager. Subclasses should not need to
     * override this - we might make it final. The triple is added using performAdd,
     * and notification done by notifyAdd.
     */
    @Override
    public void add(Triple t) {
        checkOpen();
        performAdd(t);
        notifyAdd(t);
    }

    /**
     * Add a triple to the triple store. The default implementation throws an
     * AddDeniedException; subclasses must override if they want to be able to add
     * triples.
     */
    @Override
    public void performAdd(Triple t) {
        throw new AddDeniedException("GraphBase::performAdd");
    }

    /**
     * Delete a triple, and notify the event manager. Subclasses should not need to
     * override this - we might make it final. The triple is added using
     * performDelete, and notification done by notifyDelete.
     */

    @Override
    public final void delete(Triple t) {
        checkOpen();
        performDelete(t);
        notifyDelete(t);
    }

    /**
     * Remove a triple from the triple store. The default implementation throws a
     * DeleteDeniedException; subclasses must override if they want to be able to
     * remove triples.
     */
    @Override
    public void performDelete(Triple t) {
        throw new DeleteDeniedException("GraphBase::delete");
    }

    /**
     * Remove all the statements from this graph.
     */
    @Override
    public void clear() {
        GraphUtil.remove(this, Node.ANY, Node.ANY, Node.ANY);
        getEventManager().notifyEvent(this, GraphEvents.removeAll);
    }

    /**
     * Remove all triples that match by find(s, p, o)
     */
    @Override
    public void remove(Node s, Node p, Node o) {
        GraphUtil.remove(this, s, p, o);
        getEventManager().notifyEvent(this, GraphEvents.remove(s, p, o));
    }

    @Override
    public final ExtendedIterator<Triple> find(Triple m) {
        checkOpen();
        return graphBaseFind(m);
    }

    protected abstract ExtendedIterator<Triple> graphBaseFind(Triple triplePattern);

    public ExtendedIterator<Triple> forTestingOnly_graphBaseFind(Triple t) {
        return graphBaseFind(t);
    }

    @Override
    public final ExtendedIterator<Triple> find(Node s, Node p, Node o) {
        checkOpen();
        return graphBaseFind(s, p, o);
    }

    protected ExtendedIterator<Triple> graphBaseFind(Node s, Node p, Node o) {
        return find(Triple.createMatch(s, p, o));
    }

    /**
     * Answer <code>true</code> iff <code>t</code> is in the graph as revealed by
     * <code>find(t)</code> being non-empty. <code>t</code> may contain ANY
     * wildcards. Sub-classes may over-ride graphBaseContains for efficiency.
     */
    @Override
    public final boolean contains(Triple t) {
        checkOpen();
        return graphBaseContains(t);
    }

    /**
     * Answer true if the graph contains any triple matching <code>t</code>. The
     * default implementation uses <code>find</code> and checks to see if the
     * iterator is non-empty.
     */
    protected boolean graphBaseContains(Triple t) {
        return containsByFind(t);
    }

    /**
     * Answer <code>true</code> if this graph contains <code>(s, p, o)</code>; this
     * canonical implementation cannot be over-ridden.
     */
    @Override
    public final boolean contains(Node s, Node p, Node o) {
        checkOpen();
        return contains(Triple.createMatch(s, p, o));
    }

    /**
     * Utility method: answer true iff we can find at least one instantiation of the
     * triple in this graph using find(TripleMatch).
     *
     * @param t Triple that is the pattern to match
     * @return true iff find(t) returns at least one result
     */
    final protected boolean containsByFind(Triple t) {
        ClosableIterator<Triple> it = find(t);
        try {
            return it.hasNext();
        } finally {
            it.close();
        }
    }

    /**
     * Answer the size of this graph (ie the number of exposed triples). Defined as
     * the size of the triple store plus the size of the reification store.
     * Subclasses must override graphBaseSize() to reimplement (and reifierSize if
     * they have some special reason for redefined that).
     */
    @Override
    public final int size() {
        checkOpen();
        return graphBaseSize();
    }

    /**
     * Answer the number of triples in this graph. Default implementation counts its
     * way through the results of a findAll. Subclasses must override if they want
     * size() to be efficient.
     */
    protected int graphBaseSize() {
        ExtendedIterator<Triple> it = GraphUtil.findAll(this);
        try {
            return (int)Iter.count(it);
        } finally {
            it.close();
        }
    }

    /**
     * Answer true iff this graph contains no triples.
     *
     * @implNote The default implementation relies on {@link #contains(Triple)} with
     *     {@link Triple#ANY} as the argument. Subclasses may override if necessary.
     */
    @Override
    public boolean isEmpty() {
        return !contains(Triple.ANY);
    }

    /**
     * Answer true iff this graph is isomorphic to <code>g</code> according to the
     * algorithm (indeed, method) in <code>GraphMatcher</code>.
     */
    @Override
    public boolean isIsomorphicWith(Graph g) {
        checkOpen();
        return g != null && GraphMatcher.equals(this, g);
    }

    /**
     * Answer a human-consumable representation of this graph. Not advised for big
     * graphs, as it generates a big string: intended for debugging purposes.
     */

    @Override
    public String toString() {
        return toString((closed ? "closed " : ""), this);
    }

    /**
     * toString will not cut off up to this number of triples.
     */
    public static final int TOSTRING_TRIPLE_BASE = 10;

    /**
     * toString will not output more than this number of triples.
     */
    public static final int TOSTRING_TRIPLE_LIMIT = 17;

    /**
     * Answer a human-consumable representation of <code>that</code>. The string
     * <code>prefix</code> will appear near the beginning of the string. Nodes may be
     * prefix-compressed using <code>that</code>'s prefix-mapping. This default
     * implementation will display all the triples exposed by the graph (ie including
     * reification triples if it is Standard).
     */
    public static String toString(String prefix, Graph that) {
        PrefixMapping pm = that.getPrefixMapping();
        StringBuilder b = new StringBuilder(prefix + " {");
        int count = 0;
        String gap = "";
        ClosableIterator<Triple> it = GraphUtil.findAll(that);
        while (it.hasNext() && count < TOSTRING_TRIPLE_LIMIT) {
            b.append(gap);
            gap = "; ";
            count += 1;
            b.append(it.next().toString(pm));
        }
        if ( it.hasNext() )
            b.append("...");
        it.close();
        b.append("}");
        return b.toString();
    }
}
